using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;

namespace Decav.Reality
{
    /// <summary>
    /// Defines a state in the United States of America, including its name and postal code.
    /// </summary>
    [Serializable]
    public struct UsState
    {
        /// <summary>
        /// Initializes a new <see cref="UsState"/> value.
        /// </summary>
        /// <param name="englishName">The english name of the state.</param>
        /// <param name="postalCode">The postal code of the state.</param>
        private UsState(string englishName, string postalCode)
	    {
            if ( englishName == null )
                throw new ArgumentNullException( "englishName" );
        
            if ( postalCode == null )
                throw new ArgumentNullException( "postalCode" );

            if (postalCode.Length != 2)
                throw new ArgumentException("State postal code must be two characters in length.");

            _englishName = englishName;
            _postalCode = postalCode;
	    }

        private string _englishName;
        private string _postalCode;


        private static readonly Collection<UsState> _states = new Collection<UsState>();
        private static readonly Dictionary<string, UsState> _statesByCode = new Dictionary<string, UsState>();
        private static ReadOnlyCollection<UsState> _statesReadOnly;

        private static void Initialize()
        {
            lock (_states)
            {
                if (_states.Count != 0)
                    return;

                _states.Add(new UsState("Alabama", "AL"));
                _states.Add(new UsState("Alaska", "AK"));
                _states.Add(new UsState("Arizona", "AZ"));
                _states.Add(new UsState("Arkansas", "AR"));
                _states.Add(new UsState("California", "CA"));
                _states.Add(new UsState("Colorado", "CO"));
                _states.Add(new UsState("Connecticut", "CT"));
                _states.Add(new UsState("District of Columbia (DC)", "DC"));
                _states.Add(new UsState("Delaware", "DE"));
                _states.Add(new UsState("Florida", "FL"));
                _states.Add(new UsState("Georgia", "GA"));
                _states.Add(new UsState("Hawaii", "HI"));
                _states.Add(new UsState("Idaho", "ID"));
                _states.Add(new UsState("Illinois", "IL"));
                _states.Add(new UsState("Indiana", "IN"));
                _states.Add(new UsState("Iowa", "IA"));
                _states.Add(new UsState("Kansas", "KS"));
                _states.Add(new UsState("Kentucky", "KY"));
                _states.Add(new UsState("Louisiana", "LA"));
                _states.Add(new UsState("Maine", "ME"));
                _states.Add(new UsState("Maryland", "MD"));
                _states.Add(new UsState("Massachusetts", "MA"));
                _states.Add(new UsState("Michigan", "MI"));
                _states.Add(new UsState("Minnesota", "MN"));
                _states.Add(new UsState("Mississippi", "MS"));
                _states.Add(new UsState("Missouri", "MO"));
                _states.Add(new UsState("Montana", "MT"));
                _states.Add(new UsState("Nebraska", "NE"));
                _states.Add(new UsState("Nevada", "NV"));
                _states.Add(new UsState("New Hampshire", "NH"));
                _states.Add(new UsState("New Jersey", "NJ"));
                _states.Add(new UsState("New Mexico", "NM"));
                _states.Add(new UsState("New York", "NY"));
                _states.Add(new UsState("North Carolina", "NC"));
                _states.Add(new UsState("North Dakota", "ND"));
                _states.Add(new UsState("Ohio", "OH"));
                _states.Add(new UsState("Oklahoma", "OK"));
                _states.Add(new UsState("Oregon", "OR"));
                _states.Add(new UsState("Pennsylvania", "PA"));
                _states.Add(new UsState("Rhode Island", "RI"));
                _states.Add(new UsState("South Carolina", "SC"));
                _states.Add(new UsState("South Dakota", "SD"));
                _states.Add(new UsState("Tennessee", "TN"));
                _states.Add(new UsState("Texas", "TX"));
                _states.Add(new UsState("Utah", "UT"));
                _states.Add(new UsState("Vermont", "VT"));
                _states.Add(new UsState("Virginia", "VA"));
                _states.Add(new UsState("Washington", "WA"));
                _states.Add(new UsState("West Virginia", "WV"));
                _states.Add(new UsState("Wisconsin", "WI"));
                _states.Add(new UsState("Wyoming", "WY"));

                foreach (UsState s in _states)
                    _statesByCode.Add(s._postalCode, s);

                _statesReadOnly = new ReadOnlyCollection<UsState>(_states);
            }
        }

        /// <summary>
        /// Gets a <see cref="UsState"/> by the specified two-letter postal code.
        /// </summary>
        /// <param name="code">The two-letter USPS postal code.</param>
        /// <returns>The US state defined for the specified code.</returns>
        public static UsState FromPostalCode(string postalCode)
        {
            Initialize();

            if (postalCode == null)
                throw new ArgumentNullException("postalCode");

            if (postalCode.Length != 2)
                throw new ArgumentException("State postal code must be two characters in length.");

            UsState state = UsState.Empty;
            if (!_statesByCode.TryGetValue(postalCode.ToUpper(), out state))
                throw new ArgumentException(string.Format("There is no state with the postal code '{0}' specified.", postalCode));

            return state;
        }

        /// <summary>
        /// The empty value of the <see cref="UsState"/> struct.
        /// </summary>
        public static readonly UsState Empty = new UsState();

        /// <summary>
        /// Gets the english name of the state.
        /// </summary>
        public string EnglishName
        {
            get
            {
                return _englishName;
            }
        }

        /// <summary>
        /// Gets the USPS postal code of the state.
        /// </summary>
        public string PostalCode
        {
            get
            {
                return _postalCode;
            }
        }

        /// <summary>
        /// Gets a collection of all US states.
        /// </summary>
        public static ReadOnlyCollection<UsState> States
        {
            get
            {
                Initialize();
                return _statesReadOnly;
            }
        }

        /// <summary>
        /// Gets the US state represented as a string of its english name.
        /// </summary>
        /// <returns>The english name of the string.</returns>
        public override string ToString()
        {
            if (_englishName == null)
                return string.Empty;

            return _englishName;
        }

        /// <summary>
        /// Checks the equality of two states.
        /// </summary>
        /// <param name="obj">The state to check.</param>
        /// <returns>True when equal, otherwise false.</returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (obj.GetType() != typeof(UsState))
                return false;

            UsState state = (UsState)obj;

            return state._postalCode == this._postalCode;
        }

        public override int GetHashCode()
        {
            if (_postalCode == null)
                return string.Empty.GetHashCode();

            return _postalCode.GetHashCode();
        }

        public static bool operator ==(UsState left, UsState right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(UsState left, UsState right)
        {
            return !left.Equals(right);
        }
    }
}
